﻿using System.Diagnostics;
using OnDash.Windowing.Common;
using OnDash.Windowing.Common.Events;
using OnDash.Windowing.GraphicsLibraryFramework;

namespace OnDash.Windowing.Desktop;

public class GameWindow : NativeWindow
{
    private const float MaxFrequency = 500.0f;

    private readonly Stopwatch _watchRender = new();
    private readonly Stopwatch _watchUpdate = new();

    private float _renderFrequency;

    private Thread _renderThread;

    private float _updateEpsilon;
    private float _updateFrequency;

    [Obsolete("Obsolete")]
    public GameWindow(GameWindowSettings gameWindowSettings, NativeWindowSettings nativeWindowSettings)
        : base(nativeWindowSettings)
    {
        IsMultiThreaded = gameWindowSettings.IsMultiThreaded;

        RenderFrequency = gameWindowSettings.RenderFrequency;
        UpdateFrequency = gameWindowSettings.UpdateFrequency;
    }

    protected bool IsRunningSlowly { get; private set; }

    [Obsolete(
        "There is not one size fits all multithreading solution, especially for OpenGL. This option will be removed in future versions, and you will have to implement what you need instead.")]
    public bool IsMultiThreaded { get; }

    public float RenderFrequency
    {
        get => _renderFrequency;
        init
        {
            switch (value)
            {
                case <= 1:
                    _renderFrequency = 0f;
                    break;
                case <= MaxFrequency:
                    _renderFrequency = value;
                    break;
                default:
                    Debug.Print("Target render frequency clamped to {0}Hz.", MaxFrequency);
                    _renderFrequency = MaxFrequency;
                    break;
            }
        }
    }

    public float RenderTime { get; protected set; }
    public float UpdateTime { get; protected set; }

    public float UpdateFrequency
    {
        get => _updateFrequency;
        set
        {
            if (value < 1) _updateFrequency = 0f;
            else if (value <= MaxFrequency)
            {
                _updateFrequency = value;
            }
            else
            {
                Debug.Print("Target render frequency clamped to {0}Hz.", MaxFrequency);
                _updateFrequency = MaxFrequency;
            }
        }
    }

    public event Action Load;
    public event Action Unload;
    public event Action<FrameEventArgs> UpdateFrame;

    public event Action RenderThreadStarted;

    public event Action<FrameEventArgs> RenderFrame;

    [Obsolete("Obsolete")]
    public virtual unsafe void Run()
    {
        Context.MakeCurrent();
        OnLoad();
        OnResize(new ResizeEventArgs(Size));

        Debug.Print("Entering main loop.");
        if (IsMultiThreaded)
        {
            Context?.MakeNoneCurrent();

            _renderThread = new Thread(StartRenderThread);
            _renderThread.Start();
        }

        _watchRender.Start();
        _watchUpdate.Start();
        while (Glfw.WindowShouldClose(WindowPtr) == false)
        {
            var timeToNextUpdateFrame = DispatchUpdateFrame();

            var sleepTime = timeToNextUpdateFrame;
            if (!IsMultiThreaded)
            {
                var timeToNextRenderFrame = DispatchRenderFrame();
                sleepTime = Math.Min(sleepTime, timeToNextRenderFrame);
            }

            if (sleepTime > 0)
                Thread.Sleep((int)Math.Floor(sleepTime * 1000));
        }

        OnUnload();
    }

    private unsafe void StartRenderThread()
    {
        Context.MakeCurrent();

        OnRenderThreadStarted();
        _watchRender.Start();
        while (Glfw.WindowShouldClose(WindowPtr) == false)
        {
            DispatchRenderFrame();
        }
    }

    private float DispatchUpdateFrame()
    {
        var isRunningSlowlyRetries = 4;
        var elapsed = (float)_watchUpdate.Elapsed.TotalSeconds;

        var updatePeriod = UpdateFrequency == 0 ? 0 : 1 / UpdateFrequency;

        while (elapsed > 0 && elapsed + _updateEpsilon >= updatePeriod)
        {
            ProcessInputEvents();
            ProcessWindowEvents(IsEventDriven);

            _watchUpdate.Restart();
            UpdateTime = elapsed;
            OnUpdateFrame(new FrameEventArgs(elapsed));

            _updateEpsilon += elapsed - updatePeriod;

            if (UpdateFrequency <= float.Epsilon) break;

            IsRunningSlowly = _updateEpsilon >= updatePeriod;

            if (IsRunningSlowly && --isRunningSlowlyRetries == 0)
            {
                _updateEpsilon = 0;
                break;
            }

            elapsed = (float)_watchUpdate.Elapsed.TotalSeconds;
        }

        return UpdateFrequency == 0 ? 0 : updatePeriod - elapsed;
    }

    private float DispatchRenderFrame()
    {
        var elapsed = _watchRender.Elapsed.TotalSeconds;
        var renderPeriod = RenderFrequency == 0 ? 0 : 1 / RenderFrequency;
        if (!(elapsed > 0) || !(elapsed >= renderPeriod))
            return (float)(RenderFrequency == 0 ? 0 : renderPeriod - elapsed);
        _watchRender.Restart();
        RenderTime = (float)elapsed;
        OnRenderFrame(new FrameEventArgs((float)elapsed));

        if (VSync == VSyncMode.Adaptive) Glfw.SwapInterval(IsRunningSlowly ? 0 : 1);

        return (float)(RenderFrequency == 0 ? 0 : renderPeriod - elapsed);
    }

    public virtual void SwapBuffers()
    {
        if (Context == null)
            throw new InvalidOperationException("Cannot use SwapBuffers when running with ContextAPI.NoAPI.");

        Context.SwapBuffers();
    }

    public new void Close()
    {
        base.Close();
    }

    protected virtual void OnRenderThreadStarted()
    {
        RenderThreadStarted.Invoke();
    }

    protected virtual void OnLoad()
    {
        Load.Invoke();
    }

    protected virtual void OnUnload()
    {
        Unload.Invoke();
    }

    protected virtual void OnUpdateFrame(FrameEventArgs args)
    {
        UpdateFrame.Invoke(args);
    }

    protected virtual void OnRenderFrame(FrameEventArgs args)
    {
        RenderFrame.Invoke(args);
    }
}